<!-- Copyright (c) 2022 渝州大数据实验室
 *
 * Lanius is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *     http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 -->
<!-- 
* @desc:下拉树组件
* @fileName:SelectTree.vue
* @author:chenxiaofeng@yzbdl.ac.cn
* @date:2022-4-19
!-->
<template>
  <div
    ref="selectRef"
    style="position: relative; width: 100%"
    :class="{ 'm-active': isShow || showClearBtn }"
  >
    <div
      class="m-select-input"
      @click="toggle"
      @mouseover="() => (showClearBtn = true)"
      @mouseout="() => (showClearBtn = false)"
    >
      <el-input
        v-show="!isShow"
        v-model="label"
        type="text"
        size="large"
        class="input-sm"
        :class="!disabled ? 'bg-white' : ''"
        :disabled="disabled"
        :placeholder="placeholder"
        readonly
      />
      <!--      搜索-->
      <el-input
        v-show="isShow"
        ref="searchInputRef"
        v-model="searchValue"
        type="text"
        size="large"
        clearable
        class="input-sm"
        :disabled="disabled"
        :readonly="!filterable"
        :placeholder="label || placeholder"
      />
      <div class="select-input-suffix">
        <span
          v-if="showClearBtn && clearable && value != undefined"
          class="el-icon-circle-close"
          @click="handleClearValue"
        />
        <span
          v-else
          :class="{ 'el-icon-arrow-up': isShow, 'el-icon-arrow-down': !isShow }"
        />
      </div>
    </div>
    <div v-show="isShow" class="drop-list-container">
      <ul class="drop-list">
        <el-scrollbar :max-height="treeHeight">
          <el-tree
            ref="treeRef"
            class="m-tree"
            :data="options"
            :current-node-key="value"
            :expand-on-click-node="false"
            :node-key="fieldValue"
            :props="{ children: childrenKey, label: fieldLabel }"
            :filter-node-method="filterNodeMethod || defaultFilterMethod"
            @node-click="handleChange"
          >
            <template #default="{ data }">
              <span class="m-tree-node" :title="data[fieldLabel]">
                <span>
                  {{ data[fieldLabel] }}
                </span>
                <span>
                  {{
                    data[childrenKey]?.length &&
                    `(${data[childrenKey]?.length})`
                  }}
                </span>
              </span>
            </template>
          </el-tree>
        </el-scrollbar>
      </ul>
    </div>
  </div>
</template>
<script lang="ts">
import { Tree } from "element-plus/es/components/tree-v2/src/types";
import {
  defineComponent,
  nextTick,
  onMounted,
  reactive,
  ref,
  toRef,
  toRefs,
  watch,
} from "vue";

export default defineComponent({
  components: {},
  props: {
    modelValue: {
      required: false,
    },
    //options
    options: {
      type: Array,
      required: true,
    },
    //字段值
    fieldValue: {
      type: String,
      default: "value",
    },
    //字段显示字段
    fieldLabel: {
      type: String,
      default: "label",
    },
    //是否禁用
    disabled: {
      type: Boolean,
      required: false,
      default: false,
    },
    //
    placeholder: {
      type: String,
      default: "请选择",
    },
    //是否可以重置
    clearable: {
      type: Boolean,
      required: false,
      default: true,
    },
    //是否可以搜索
    filterable: {
      type: Boolean,
      required: false,
      default: true,
    },
    //自定义搜索方法
    filterNodeMethod: {
      type: Array,
      required: false,
    },
    //树下拉宽度
    treeHeight: {
      type: String,
      default: "250px",
    },
    //孩子key
    childrenKey: {
      type: String,
      required: false,
      default: "children",
    },
  },
  emits: ["change", "update:modelValue"],
  setup(props, { emit }) {
    //状态
    const state = reactive({
      //是否显示下拉
      isShow: false,
      //组件内部管理的状态
      value: undefined as any,
      //显示
      label: undefined as any,
      //是否显示clear按钮
      showClearBtn: false,
      //搜索值
      searchValue: "",
      //树宽度
      treeWidthState: "200px",
    });

    const refProps = toRefs(props);

    const { disabled, fieldLabel, fieldValue, options, filterable } = refProps;

    //
    const selectRef = ref();

    //搜索框
    const searchInputRef = ref();

    //树ref
    const treeRef = ref();

    //不是点击本元素，点击别处自动关闭下拉
    const handleDocumentClick = (e: { target: any }) => {
      if (!state.isShow || !selectRef.value) {
        return;
      }
      //查找该节点下面的所有子节点，并且判断事件源target节点是否在当前select 节点中
      const inNode = [
        ...(selectRef.value.querySelectorAll("*") || []),
      ].includes(e.target);
      if (!inNode) {
        hide();
      }
    };

    //注册事件
    onMounted(() => {
      document.addEventListener("click", handleDocumentClick);
      document.addEventListener("touchstart", handleDocumentClick);
    });

    //显示下拉
    const show = () => {
      if (disabled.value === false) {
        state.isShow = true;
        // 自动聚焦
        nextTick(() => {
          searchInputRef.value.focus();
        });
      }
    };

    //关闭下拉
    const hide = () => {
      state.isShow = false;
      if (filterable.value) {
        state.searchValue = "";
      }
    };

    //切换下拉是否显示
    const toggle = () => {
      if (disabled.value === false) {
        state.isShow = !state.isShow;
        if (filterable.value) {
          if (state.isShow) {
            nextTick(() => {
              searchInputRef.value.focus();
            });
          } else {
            state.searchValue = "";
          }
        }
      }
    };

    //默认过滤方法
    const defaultFilterMethod = (
      value: any,
      data: { [x: string]: string | any[] }
    ) => {
      if (!value) return true;
      return data[fieldLabel.value].indexOf(value) !== -1;
    };

    //改变回调
    const handleChange = (option: { [x: string]: undefined }) => {
      // if (option[fieldValue.value] === state.value) {
      //   return;
      // }
      state.value = option[fieldValue.value];
      state.label = option[fieldLabel.value];
      emit("change", state.value, option);
      emit("update:modelValue", state.value);
      hide();
    };

    //清理值
    const handleClearValue = () => {
      state.label = undefined;
      state.value = undefined;
      emit("change");
      emit("update:modelValue");
    };

    //查找options
    const findOption = (options: unknown[], key: unknown) => {
      let item;
      const search = (data: string | any[]) => {
        if (!data || data.length == 0) {
          return;
        }
        for (let i = 0; i < data.length; i++) {
          const datum = data[i];
          if (datum[fieldValue.value] === key) {
            item = datum;
            return;
          }
          search(datum[refProps.childrenKey.value]);
        }
      };
      search(options);
      return item;
    };

    watch(
      refProps.modelValue,
      (newValue, oldValue) => {
        if (newValue === state.value) {
          return;
        }
        const option = findOption(options.value, newValue);
        state.label = option ? option[fieldLabel.value] : newValue;
        state.value = option ? option[fieldValue.value] : newValue;
        treeRef.value?.setCurrentKey(newValue == "" ? null : newValue);
      },
      {
        immediate: true,
      }
    );

    //监听搜索框的值，并且过滤处有效选项
    watch(toRef(state, "searchValue"), (value, oldValue) => {
      if (oldValue == undefined) {
        return;
      }
      treeRef.value.filter(value);
    });

    return {
      ...toRefs(state),
      ...refProps,
      selectRef,
      show,
      hide,
      toggle,
      handleChange,
      handleClearValue,
      searchInputRef,
      treeRef,
      defaultFilterMethod,
    };
  },
});
</script>
<style lang="scss" scoped>
:deep(.el-tree-node__content) {
  height: 34px;
}
:deep(.el-tree-node) {
  line-height: 34px;
  &:hover {
    background-color: var(--el-fill-color-light);
  }
}
.m-select-input {
  position: relative;
  min-width: 200px;
  width: 100%;
  height: 40px;
  > * {
    cursor: pointer;
  }
  & * {
    box-sizing: border-box;
  }
  .form-control {
    width: 100%;
    height: 100%;
    //background-color: #e5e5e5;
    color: #50545f;
    padding: 3px 30px 3px 5px !important;
    border: 1px solid #dedfe6;
    border-radius: 2px;
    &:focus {
      border: 1px #2165f9 solid;
    }
  }
  .select-input-suffix {
    display: flex;
    align-items: center;
    align-content: center;
    align-self: center;
    position: absolute;
    height: 100%;
    right: 0;
    bottom: 0;
    top: 0;
    width: 25px;
    > span {
      color: #e5e5e5;
    }
  }
}
.drop-list-container {
  z-index: 2000;
  position: absolute;
  border: 1px solid #e5e5e5;
  left: 0;
  right: 0;
  top: 41px;
  .drop-list {
    background: #ffffff;
    list-style-type: none;
    width: 100%;
    overflow: hidden;
    margin-bottom: 0px;
    margin-top: 0px;
    padding: 6px 0px;
    &::-webkit-scrollbar {
      width: 5px;
    }
    &::-webkit-scrollbar-thumb {
      border-radius: 4px;
      background: rgba(0, 0, 0, 0.2);
    }
    &::-webkit-scrollbar-track {
      background: #edf2fa;
    }
    .drop-item {
      padding: 3px 15px;
      font-size: 12px;
      font-weight: 400;
      line-height: 24px;
      color: #50545f;
      background: #edf2fa;
      &:hover,
      &._active {
        background: linear-gradient(-90deg, #e5e5e5, #48a5ff);
        color: #2165f9;
        opacity: 0.5;
        cursor: pointer;
      }
    }
  }
  .m-tree {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}
//整个dom聚焦状态
.m-active {
  .form-control {
    border: 1px #2165f9 solid;
  }
  .select-input-suffix {
    > span {
      color: #2165f9;
    }
  }
}
</style>
